package com.siashan.unipay4j.wx.bill;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.siashan.unipay4j.core.PayChannel;
import com.siashan.unipay4j.core.bill.BillModel;
import com.siashan.unipay4j.core.bill.BillResponse;
import com.siashan.unipay4j.core.bill.IBillDownload;
import com.siashan.unipay4j.core.IPayConfig;
import com.siashan.unipay4j.core.Result;
import com.siashan.unipay4j.core.UnipayConst;
import com.siashan.unipay4j.core.pay.PayQueryModel;
import com.siashan.unipay4j.core.util.DateUtil;
import com.siashan.unipay4j.core.util.EnumUtil;
import com.siashan.unipay4j.core.util.StrUtil;
import com.siashan.unipay4j.wx.AbstractWxPay;
import com.siashan.unipay4j.wx.WxPayVersionEnum;
import com.siashan.unipay4j.wx.model.QueryOrderResult;
import com.siashan.unipay4j.wx.pay.WxTradeStateEnum;
import com.siashan.unipay4j.wx.sdk.WXPayConstants;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.StatusLine;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;

import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Map;

import static org.apache.http.HttpStatus.SC_MULTIPLE_CHOICES;
import static org.apache.http.HttpStatus.SC_OK;

/**
 * 微信对账单下载
 *
 * <p>下载交易账单</p>
 * <p>
 * 商户可以通过该接口下载历史交易清单。比如掉单、系统错误等导致商户侧和微信侧数据不一致，通过对账单核对后可校正支付状态。
 * <p>
 * 注意：
 * <p>
 * 1、微信侧未成功下单的交易不会出现在对账单中。支付成功后撤销的交易会出现在对账单中，跟原支付单订单号一致；
 * <p>
 * 2、微信在次日9点启动生成前一天的对账单，建议商户10点后再获取；
 * <p>
 * 3、对账单中涉及金额的字段单位为“元”。
 * <p>
 * 4、对账单接口只能下载三个月以内的账单。
 * <p>
 * 5、对账单是以商户号纬度来生成的，如一个商户号与多个appid有绑定关系，则使用其中任何一个appid都可以请求下载对账单。对账单中的appid取自交易时候提交的appid，与请求下载对账单时使用的appid无关。
 * <p>
 * 6、自2018年起入驻的商户默认是开通免充值券后的结算对账单。
 *
 * <p>下载资金账单</p>
 * <p>
 * 商户可以通过该接口下载自2017年6月1日起 的历史资金流水账单。
 * <p>
 * 说明：
 * <p>
 * 1、资金账单中的数据反映的是商户微信账户资金变动情况；
 * <p>
 * 2、当日账单在次日上午9点开始生成，建议商户在上午10点以后获取；
 * <p>
 * 3、资金账单中涉及金额的字段单位为“元”。
 *
 * @author siashan
 * @since v1.0.1
 **/
@Slf4j
public class WxPayBillDownload extends AbstractWxPay implements IBillDownload {
    public WxPayBillDownload() {
    }

    public WxPayBillDownload(IPayConfig payConfig) {
        super(payConfig);
    }

    /**
     * 下载对账单
     *
     * @param model 账单请求参数
     * @return 对账单
     */
    @Override
    public Result<BillResponse> execDown(BillModel model) {
        WxPayVersionEnum ver = getWxPayVersion();
        Result result = null;
        switch (ver) {
            case V2:
                result = execDownV2(model);
                break;
            case V3:
                result = execDownV3(model);
                break;
            default:
                break;
        }
        return result;
    }

    public Result<BillResponse> execDownV2(BillModel billModel) {
        log.info("WxBillDownload::=>请求到达，账单日期{},账单类型{}", billModel.getBillDate());
        try {
            Map<String, String> map = new HashMap<>();
            map.put("bill_date", billModel.getBillDate());
            map.put("bill_type", "ALL");
            Map<String, String> r = initWxPay().downloadBill(map);
            String returnCode = r.get(WXPayConstants.RETURN_CODE);
            if (isReturnSuccess(returnCode)) {
                // 对账单内容
                Result.tradeSuccess(wrapBillResponse(r));
            } else {
                String returnMsg = r.get("return_msg");
                log.info("WxBillDownload::=>请求失败，账单日期{} ,错误代码{},错误原因{}", billModel.getBillDate(), returnCode, returnMsg);
                return Result.returnFail(returnCode, returnMsg);
            }

        } catch (Exception e) {
            log.info("WxBillDownload::=>下载对账单发生异常，账单日期{}", billModel.getBillDate());
        }
        return Result.exception();
    }

    /**
     * 下载对账单
     *
     * @param model 注意：
     *              • 微信侧未成功下单的交易不会出现在对账单中。支付成功后撤销的交易会出现在对账单中，跟原支付单订单号一致；
     *              <p>
     *              • 对账单中涉及金额的字段单位为“元”；
     *              <p>
     *              • 对账单接口只能下载三个月以内的账单。
     * @return
     */
    public Result<BillResponse> execDownV3(BillModel model) {
        log.info("WxBillDownload::=>请求到达，账单日期{},账单类型{}", model.getBillDate());
        WechatPayHttpClientBuilder builder = loadBuilder();
        CloseableHttpClient httpClient = builder.build();
        HttpGet httpGet = getHttpGet(model);
        try {
            CloseableHttpResponse response = httpClient.execute(httpGet);
            StatusLine statusLine = response.getStatusLine();
            String body = EntityUtils.toString(response.getEntity());
            log.info("WxBillDownload::=>账单日期{} 返回报文，{}", model.getBillDate(), body);
            if (statusLine.getStatusCode() >= SC_OK && statusLine.getStatusCode() < SC_MULTIPLE_CHOICES) {
                WxBillResponse billResponse = JSON.parseObject(body,WxBillResponse.class);
                // TODO
            } else {
                JSONObject bodyJson = JSON.parseObject(body);
                String errCode = bodyJson.getString(WXPayConstants.CODE);
                String errCodeDes = bodyJson.getString(WXPayConstants.MESSAGE);
                log.info("WxBillDownload::=>账单日期{} 请求失败,错误代码{},错误原因{}", model.getBillDate(), errCode, errCodeDes);
                return Result.returnFail(errCode, errCodeDes);
            }
        } catch (Exception e) {
            log.warn("WxBillDownload=>账单日期{} 请求时发生异常", model.getBillDate(), e);
        }
        return Result.exception();
    }

    private HttpGet getHttpGet(BillModel model) {
        WxBillModel wxBillModel = new WxBillModel();
        wxBillModel.setBillDate(model.getBillDate());
        if (StrUtil.isNotEmpty(model.getBillType())) {
            WxBilltypeEnum billtypeEnum = EnumUtil.getIEnum(WxBilltypeEnum.class, model.getBillType());
            if (null == billtypeEnum) {
                throw new RuntimeException("不支持的账单类型");
            }
            wxBillModel.setBillType(billtypeEnum);
        }
        if (StrUtil.isNotEmpty(model.getTarType())) {
            WxBillTartypeEnum tartypeEnum = EnumUtil.getIEnum(WxBillTartypeEnum.class, model.getTarType());
            if (null == tartypeEnum) {
                throw new RuntimeException("不支持的账单压缩类型");
            }
            wxBillModel.setTarType(tartypeEnum);
        }
        return getHttpGet(wxBillModel);
    }

    private HttpGet getHttpGet(WxBillModel model) {
        try {
            URIBuilder uriBuilder = new URIBuilder(WXPayConstants.V3_PAY_URL_TRADEBILL);
            uriBuilder.setParameter("bill_date", model.getBillDate());
            if (null != model.getBillType()) {
                uriBuilder.setParameter("bill_type", model.getBillType().getValue());
            }
            if (null != model.getTarType()) {
                uriBuilder.setParameter("tar_type", model.getTarType().getValue());
            }
            return new HttpGet(uriBuilder.build());
        } catch (URISyntaxException e) {
            log.warn("构建交易账单请求Url异常", e);
            throw new RuntimeException();
        }
    }

    private BillResponse wrapBillResponse(Map<String, String> r) {
        BillResponse response = new BillResponse();
        response.setPayChannel(PayChannel.WX);
        response.setBillDownUrl(r.get("data"));
        return response;
    }
}
